#region Copyright Notice

// 
// 
//  
//  Copyright (c) 2009-2011 Anindya Chatterjee
//  
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//  
//      http://www.apache.org/licenses/LICENSE-2.0
//  
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// 
// 

#endregion

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Security.Permissions;

namespace AbstractClass.Extensions
{
    /// <summary>
    /// Provides a set of extension methods for <see cref="Bitmap"/> images.
    /// </summary>
    public static class ImageExtensions
    {
        /// <summary>
        /// Converts a bitmap image to a 2-dimensional <see cref="int"/> array, whose values
        /// are 32 bit ARGB <see cref="Color"/> values.
        /// </summary>
        /// <param name="image">The specified image.</param>
        /// <returns>A 2-dimensional array containing the color values of the specified image.</returns>
        public static int[,] ToArray(this Bitmap image)
        {
            var data = new int[image.Width,image.Height];
            for (int i = 0; i < image.Width; i++)
            {
                for (int j = 0; j < image.Height; j++)
                {
                    data[i, j] = image.GetPixel(i, j).ToArgb();
                }
            }
            return data;
        }

        /// <summary>
        /// Converts the <paramref name="image"/> to a 2-dimensional <see cref="Color"/> array.
        /// </summary>
        /// <param name="image">The specified image.</param>
        /// <returns>A 2-dimensional color array representing the <paramref name="image"/>.</returns>
        public static Color[,] ToColorArray(this Bitmap image)
        {
            var data = new Color[image.Width,image.Height];
            for (int i = 0; i < image.Width; i++)
            {
                for (int j = 0; j < image.Height; j++)
                {
                    data[i, j] = image.GetPixel(i, j);
                }
            }
            return data;
        }

#if !SAFE

        /// <summary>
        /// Converts the <paramref name="srcImg"/> to a grayscale image.
        /// </summary>
        /// <param name="srcImg">The source image.</param>
        /// <returns>A 8-bit indexed grayscale image of <paramref name="srcImg"/>.</returns>
        /// <remarks>This method has unsafe block, and not suitable for untrusted environment. For safe execution build the assembly with SAFE constant.</remarks>
        [SecurityPermission(SecurityAction.LinkDemand)]
        public static Bitmap ToGrayScale(this Bitmap srcImg)
        {
            if (srcImg.PixelFormat != PixelFormat.Format24bppRgb)
            {
                if (srcImg.PixelFormat != PixelFormat.Format8bppIndexed)
                {
                    throw new ArgumentException();
                }
                return srcImg;
            }

            int swidth = srcImg.Width;
            int sheight = srcImg.Height;
            BitmapData srcData = srcImg.LockBits(
                new Rectangle(0, 0, swidth, sheight),
                ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

            Bitmap dstImg = CreateGrayscaleImage(swidth, sheight);

            try
            {
                BitmapData dstData1 = dstImg.LockBits(
                    new Rectangle(0, 0, swidth, sheight),
                    ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);

                int srcOffset = srcData.Stride - swidth * 3;
                int dstOffset = dstData1.Stride - swidth;

                unsafe
                {
                    var src = (byte*) srcData.Scan0.ToPointer();
                    var dst = (byte*) dstData1.Scan0.ToPointer();

                    for (int y = 0; y < sheight; y++)
                    {
                        for (int x = 0; x < swidth; x++, src += 3, dst++)
                        {
                            *dst = (byte) (0.299 * src[2] + 0.587 * src[1] + 0.114 * src[0]);
                        }
                        src += srcOffset;
                        dst += dstOffset;
                    }
                }
                dstImg.UnlockBits(dstData1);
                srcImg.UnlockBits(srcData);

                return dstImg;
            }
            catch (Exception)
            {
                dstImg.Dispose();
                throw;
            }
        }

#else
#pragma warning disable 1587
    /// <summary>
    /// Converts <paramref name="original"/> to a grayscale image.
    /// </summary>
    /// <param name="original">The source image.</param>
    /// <returns>A 8-bit indexed grayscale image of <paramref name="original"/>.</returns>
    /// <remarks>This method is safe and suitable for use in unsafe environment.</remarks>
#pragma warning restore 1587
        public static Bitmap ToGrayScale(this Bitmap original)
        {
            var newBitmap = new Bitmap(original.Width, original.Height);

            for (int i = 0; i < original.Width; i++)
            {
                for (int j = 0; j < original.Height; j++)
                {
                    Color originalColor = original.GetPixel(i, j);
                    var grayScale = (int) ((originalColor.R * .3) + (originalColor.G * .59)
                                           + (originalColor.B * .11));
                    Color newColor = Color.FromArgb(grayScale, grayScale, grayScale);
                    newBitmap.SetPixel(i, j, newColor);
                }
            }

            return newBitmap;
        }

#endif

        private static Bitmap CreateGrayscaleImage(int swidth, int sheight)
        {
            var srcImg = new Bitmap(swidth, sheight, PixelFormat.Format8bppIndexed);

            try
            {
                ColorPalette cp = srcImg.Palette;
                for (int i = 0; i < 256; i++)
                {
                    cp.Entries[i] = Color.FromArgb(i, i, i);
                }
                srcImg.Palette = cp;
                return srcImg;
            }
            catch (Exception)
            {
                srcImg.Dispose();
                throw;
            }
        }

        /// <summary>
        /// Determines whether <paramref name="bmp"/> is a grayscale image.
        /// </summary>
        /// <param name="bmp">The Bitmap image to check.</param>
        /// <returns>
        /// 	<c>true</c> if the image is a grayscale image; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsGrayScale(this Bitmap bmp)
        {
            bool result = false;
            if (bmp.PixelFormat == PixelFormat.Format8bppIndexed)
            {
                result = true;
                ColorPalette cp = bmp.Palette;
                Color color;
                for (int i = 0; i < 256; i++)
                {
                    color = cp.Entries[i];
                    if (((color.R == i) && (color.G == i)) && (color.B == i)) continue;
                    result = false;
                    break;
                }
            }
            return result;
        }

        /// <summary>
        /// Creates a clone of <paramref name="src"/> with a specified <see cref="PixelFormat"/>.
        /// </summary>
        /// <param name="src">The source image.</param>
        /// <param name="format">The destination pixel format.</param>
        /// <returns>A cloned image of specified pixel format.</returns>
        [SecurityPermission(SecurityAction.LinkDemand)]
        public static Bitmap Clone(this Bitmap src, PixelFormat format)
        {
            if (src.PixelFormat == format)
                return Clone(src);

            int width = src.Width;
            int height = src.Height;

            var bmp = new Bitmap(width, height, format);

            try
            {
                Graphics g = Graphics.FromImage(bmp);
                g.DrawImage(src, 0, 0, width, height);
                g.Dispose();
                return bmp;
            }
            catch (Exception)
            {
                bmp.Dispose();
                throw;
            }
        }

        [SecurityPermission(SecurityAction.LinkDemand)]
        private static Bitmap Clone(Bitmap src)
        {
            int width = src.Width;
            int height = src.Height;
            var dst = new Bitmap(width, height, src.PixelFormat);

            try
            {
                BitmapData srcData = src.LockBits(
                    new Rectangle(0, 0, width, height),
                    ImageLockMode.ReadWrite, src.PixelFormat);

                BitmapData dstData = dst.LockBits(
                    new Rectangle(0, 0, width, height),
                    ImageLockMode.ReadWrite, dst.PixelFormat);

                NativeMethods.MemCopy(dstData.Scan0, srcData.Scan0, new UIntPtr((uint) (height * srcData.Stride)));
                dst.UnlockBits(dstData);
                src.UnlockBits(srcData);

                if ((src.PixelFormat == PixelFormat.Format1bppIndexed) ||
                    (src.PixelFormat == PixelFormat.Format4bppIndexed) ||
                    (src.PixelFormat == PixelFormat.Format8bppIndexed) ||
                    (src.PixelFormat == PixelFormat.Indexed))
                {
                    ColorPalette srcPalette = src.Palette;
                    ColorPalette dstPalette = dst.Palette;

                    int n = srcPalette.Entries.Length;

                    for (int i = 0; i < n; i++)
                    {
                        dstPalette.Entries[i] = srcPalette.Entries[i];
                    }

                    dst.Palette = dstPalette;
                }

                return dst;
            }
            catch (Exception)
            {
                dst.Dispose();
                throw;
            }
        }
    }
}